/*
 * Copyright (c) 2012 Masahiro Kozuka. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <winsock2.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <ws2spi.h>

#include <usrsctp.h>
#include <usrsctp_sp.h>

#include <stdio.h>

typedef INT (WINAPI *PWSH_ADDRESS_TO_STRING)(LPSOCKADDR,INT,LPWSAPROTOCOL_INFOW,LPWSTR,LPDWORD);
typedef INT (WINAPI *PWSH_STRING_TO_ADDRESS)(LPWSTR,DWORD,LPWSAPROTOCOL_INFOW,LPSOCKADDR,LPDWORD);

//
//  VOID
//  InitializeListHead(
//      PLIST_ENTRY ListHead
//      );
//

#define InitializeListHead(ListHead) (\
	(ListHead)->Flink = (ListHead)->Blink = (ListHead))

//
//  BOOLEAN
//  IsListEmpty(
//      PLIST_ENTRY ListHead
//      );
//

#define IsListEmpty(ListHead) \
	((ListHead)->Flink == (ListHead))

//
//  PLIST_ENTRY
//  RemoveHeadList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveHeadList(ListHead) \
	(ListHead)->Flink;\
	{RemoveEntryList((ListHead)->Flink)}

//
//  PLIST_ENTRY
//  RemoveTailList(
//      PLIST_ENTRY ListHead
//      );
//

#define RemoveTailList(ListHead) \
	(ListHead)->Blink;\
	{RemoveEntryList((ListHead)->Blink)}

//
//  VOID
//  RemoveEntryList(
//      PLIST_ENTRY Entry
//      );
//

#define RemoveEntryList(Entry) {\
	PLIST_ENTRY _EX_Blink;\
	PLIST_ENTRY _EX_Flink;\
	_EX_Flink = (Entry)->Flink;\
	_EX_Blink = (Entry)->Blink;\
	_EX_Blink->Flink = _EX_Flink;\
	_EX_Flink->Blink = _EX_Blink;\
	}

//
//  VOID
//  InsertTailList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertTailList(ListHead,Entry) {\
	PLIST_ENTRY _EX_Blink;\
	PLIST_ENTRY _EX_ListHead;\
	_EX_ListHead = (ListHead);\
	_EX_Blink = _EX_ListHead->Blink;\
	(Entry)->Flink = _EX_ListHead;\
	(Entry)->Blink = _EX_Blink;\
	_EX_Blink->Flink = (Entry);\
	_EX_ListHead->Blink = (Entry);\
	}

//
//  VOID
//  InsertHeadList(
//      PLIST_ENTRY ListHead,
//      PLIST_ENTRY Entry
//      );
//

#define InsertHeadList(ListHead,Entry) {\
	PLIST_ENTRY _EX_Flink;\
	PLIST_ENTRY _EX_ListHead;\
	_EX_ListHead = (ListHead);\
	_EX_Flink = _EX_ListHead->Flink;\
	(Entry)->Flink = _EX_Flink;\
	(Entry)->Blink = _EX_ListHead;\
	_EX_Flink->Blink = (Entry);\
	_EX_ListHead->Flink = (Entry);\
	}

typedef struct _REQUEST_RECV_ENTRY
{
	LIST_ENTRY entry;
	LPWSABUF lpBuffers;
	DWORD dwBufferCount;
	LPDWORD lpNumberOfBytesRecvd;
	LPDWORD lpFlags;
	struct sockaddr *lpFrom;
	LPINT lpFromlen;
	HANDLE hEvent;
	LPWSAOVERLAPPED lpOverlapped;
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine;
	LPWSATHREADID lpThreadId;
} REQUEST_RECV_ENTRY, *PREQUEST_RECV_ENTRY;

typedef struct _BLOCK_RECV_ENTRY
{
	LIST_ENTRY entry;
	union sctp_sockstore addr;
	char *data;
	size_t datalen;
	size_t off;
	struct sctp_rcvinfo rcv;
	int flags;
} BLOCK_RECV_ENTRY, *PBLOCK_RECV_ENTRY;

typedef struct _USRSCTP_CONTEXT
{
	struct socket *sock;
	LIST_ENTRY RecvRequests;
	LIST_ENTRY RecvBlocks;
	CRITICAL_SECTION RecvLock;
} USRSCTP_CONTEXT, *PUSRSCTP_CONTEXT;

/* Global variables. */
WSPUPCALLTABLE Upcalls;
HMODULE hIPv4Dll = NULL;
HMODULE hIPv6Dll = NULL;
static PWSH_ADDRESS_TO_STRING WSHAddressToString = NULL;
static PWSH_STRING_TO_ADDRESS WSHStringToAddress = NULL;
static PWSH_ADDRESS_TO_STRING WSHAddressToStringIPv6 = NULL;
static PWSH_STRING_TO_ADDRESS WSHStringToAddressIPv6 = NULL;


static
int
receive_cb(
	struct socket *sock,
	union sctp_sockstore addr,
	void *data,
	size_t datalen,
	struct sctp_rcvinfo rcv,
	int flags,
	void *ulp_info)
{
	PUSRSCTP_CONTEXT context = (PUSRSCTP_CONTEXT)ulp_info;
	PLIST_ENTRY entry = NULL;
	PREQUEST_RECV_ENTRY recvReq = NULL;
	PBLOCK_RECV_ENTRY recvBlock = NULL;
	size_t off, off1;
	size_t len;
	size_t i;

	fprintf(stderr, "receive_cb: enter\n");
	if (context == NULL) {
		fprintf(stderr, "receive_cb: leave#1\n");
		return 1;
	}

	fprintf(stderr, "receive_cb: before EnterCriticalSection(%p)\n", &context->RecvLock);
	EnterCriticalSection(&context->RecvLock);
	fprintf(stderr, "receive_cb: after EnterCriticalSection(%p)\n", &context->RecvLock);

	fprintf(stderr, "receive_cb: datalen=%u\n", datalen);
	for (off = 0, off1 = 0; off < datalen; off += off1, off1 = 0) {
		fprintf(stderr, "receive_cb: datalen=%u, off=%u\n", datalen, off);
		if (!IsListEmpty(&context->RecvRequests)) {
			entry = RemoveHeadList(&context->RecvRequests);
			recvReq = (PREQUEST_RECV_ENTRY)CONTAINING_RECORD(
				entry,
				REQUEST_RECV_ENTRY,
				entry);
		}
		
		fprintf(stderr, "receive_cb: recvReq=%p\n", recvReq);
		if (recvReq == NULL) {
			break;
		}

		fprintf(stderr, "receive_cb: recvReq=%p, dwBufferCount=%u\n", recvReq, recvReq->dwBufferCount);
		for (i = 0; i < recvReq->dwBufferCount && off + off1 < datalen; i++) {
			fprintf(stderr, "receive_cb: recvReq=%p, lpBuffers[%u].len=%u, datalen=%u, off=%u. off1=%u\n", recvReq, i, recvReq->lpBuffers[i].len, datalen, off, off1);
			if (recvReq->lpBuffers[i].buf != NULL &&
				recvReq->lpBuffers[i].len > 0
			   ) {
				len = recvReq->lpBuffers[i].len > datalen - off - off1 ?
					datalen - off - off1 : recvReq->lpBuffers[i].len;
				fprintf(stderr, "receive_cb: len=%u\n", len);
				memcpy(recvReq->lpBuffers[i].buf, data, len);
				off1 += len;
			}
		}
		fprintf(stderr, "receive_cb: recvReq=%p, off1=%u\n", recvReq, off1);
		*(recvReq->lpNumberOfBytesRecvd) = (DWORD)off1;

		/* Set partial flag if given buffer is shorter than received message */
		/* XXX not yet */

		/* Wake up if sync-type API used. */
		if (recvReq->hEvent != NULL) {
			fprintf(stderr, "receive_cb: before SetEvent(%p)%u\n", recvReq->hEvent);
			SetEvent(recvReq->hEvent);
		}
	}

	fprintf(stderr, "receive_cb: off=%u, datalen=%u\n", off, datalen);
	if (off < datalen) {
		recvBlock = malloc(sizeof(BLOCK_RECV_ENTRY));
		if (recvBlock == NULL) {
			LeaveCriticalSection(&context->RecvLock);
			fprintf(stderr, "receive_cb: leave#2\n");
			return 1;
		}
		memset(recvBlock, 0, sizeof(*recvBlock));
		recvBlock->data = malloc(datalen);
		if (recvBlock->data == NULL) {
			free(recvBlock);
			LeaveCriticalSection(&context->RecvLock);
			fprintf(stderr, "receive_cb: leave#3\n");
			return 1;
		}
		memcpy(recvBlock->data, data, datalen);
		recvBlock->datalen = datalen;
		recvBlock->off = off;
		memcpy(&recvBlock->addr, &addr, sizeof(addr));
		InsertTailList(&context->RecvBlocks, &recvBlock->entry);
	}

	LeaveCriticalSection(&context->RecvLock);
	fprintf(stderr, "receive_cb: leave\n");
	return 1;
}

SOCKET
WSPAPI
WSPAccept(
	SOCKET s,
	struct sockaddr FAR * addr,
	LPINT addrlen,
	LPCONDITIONPROC lpfnCondition,
	DWORD_PTR dwCallbackData,
	LPINT lpErrno)
{
	SOCKET socket = INVALID_SOCKET;

	return socket;
}

int
WSPAPI
WSPAddressToString(
	LPSOCKADDR lpsaAddress,
	DWORD dwAddressLength,
	LPWSAPROTOCOL_INFOW lpProtocolInfo,
	LPWSTR lpszAddressString,
	LPDWORD lpdwAddressStringLength,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;

	return ret;
}

int
WSPAPI
WSPAsyncSelect(
	SOCKET s,
	HWND hWnd,
	unsigned int wMsg,
	long lEvent,
	LPINT lpErrno)
{
	*lpErrno = WSAENETDOWN;
	return SOCKET_ERROR;
}

int
WSPAPI
WSPBind(
	SOCKET s,
	const struct sockaddr *name,
	int namelen,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPCancelBlockingCall(
	LPINT lpErrno)
{
	*lpErrno = WSAEOPNOTSUPP;
	return SOCKET_ERROR;
}

int
WSPAPI
WSPCleanup(
	LPINT lpErrno)
{
	if (hIPv4Dll != NULL) {
		FreeLibrary(hIPv4Dll);
	}
	if (hIPv6Dll != NULL) {
		FreeLibrary(hIPv6Dll);
	}

	*lpErrno = ERROR_SUCCESS;

	return 0;
}

int
WSPAPI
WSPCloseSocket(
	SOCKET s,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	PUSRSCTP_CONTEXT context = NULL;

	fprintf(stderr, "WSPCloseSocket: enter\n");
	if (Upcalls.lpWPUQuerySocketHandleContext(s, (PDWORD_PTR)&context, lpErrno) == SOCKET_ERROR) {
		fprintf(stderr, "WSPCloseSocket: leave#1\n");
		return ret;
	}
	if (context == NULL) {
		fprintf(stderr, "WSPCloseSocket: leave#2\n");
		return ret;
	}

	if (context->sock == NULL) {
		fprintf(stderr, "WSPCloseSocket: leave#3\n");
		return ret;
	}
	usrsctp_close(context->sock);
	ret = 0;

	fprintf(stderr, "WSPCloseSocket: leave\n");
	return ret;
}

int
WSPAPI
WSPConnect(
	SOCKET s,
	const struct sockaddr *name,
	int namelen,
	LPWSABUF lpCallerData,
	LPWSABUF lpCalleeData,
	LPQOS lpSQOS,
	LPQOS lpGQOS,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR, ret1 = -1;
	PUSRSCTP_CONTEXT context = NULL;
	struct socket *sock = NULL;

	fprintf(stderr, "WSPConnect: enter\n");
	if (Upcalls.lpWPUQuerySocketHandleContext(s, (PDWORD_PTR)&context, lpErrno) == SOCKET_ERROR) {
		fprintf(stderr, "WSPConnect: leave#1\n");
		return ret;
	}
	if (context == NULL) {
		fprintf(stderr, "WSPConnect: leave#2\n");
		return ret;
	}

	if (context->sock == NULL) {
		fprintf(stderr, "WSPConnect: leave#3\n");
		return ret;
	}

	ret1 = usrsctp_connect(context->sock, (struct sockaddr *)name, namelen);
	if (ret1 ==0) {
		ret = 0;
	}

	return ret;
}

int
WSPAPI
WSPDuplicateSocket(
	SOCKET s,
	DWORD dwProcessId,
	LPWSAPROTOCOL_INFO lpProtocolInfo,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPEnumNetworkEvents(
	SOCKET s,
	WSAEVENT hEventObject,
	LPWSANETWORKEVENTS lpNetworkEvents,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPEventSelect(
	SOCKET s,
	WSAEVENT hEventObject,
	long lNetworkEvents,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

BOOL
WSPAPI
WSPGetOverlappedResult(
	SOCKET s,
	LPWSAOVERLAPPED lpOverlapped,
	LPDWORD lpcbTransfer,
	BOOL fWait,
	LPDWORD lpdwFlags,
	LPINT lpErrno)
{
	return FALSE;
}

int
WSPAPI
WSPGetPeerName(
	SOCKET s,
	struct sockaddr *name,
	LPINT namelen,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPGetSockName(
	SOCKET s,
	struct sockaddr *name,
	LPINT namelen,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPGetSockOpt(
	SOCKET s,
	int level,
	int optname,
	char *optval,
	LPINT optlen,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

BOOL
WSPAPI
WSPGetQOSByName(
	SOCKET s,
	LPWSABUF lpQOSName,
	LPQOS lpQOS,
	LPINT lpErrno)
{
	*lpErrno = WSAENETDOWN;
	return FALSE;
}

int
WSPAPI
WSPIoctl(
	SOCKET s,
	DWORD dwIoControlCode,
	LPVOID lpvInBuffer,
	DWORD cbInBuffer,
	LPVOID lpvOutBuffer,
	DWORD cbOutBuffer,
	LPDWORD lpcbBytesReturned,
	LPWSAOVERLAPPED lpOverlapped,
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
	LPWSATHREADID lpThreadId,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

SOCKET
WSPAPI
WSPJoinLeaf(
	SOCKET s,
	const struct sockaddr* name,
	int namelen,
	LPWSABUF lpCallerData,
	LPWSABUF lpCalleeData,
	LPQOS lpSQOS,
	LPQOS lpGQOS,
	DWORD dwFlags,
	LPINT lpErrno)
{
	*lpErrno = WSAENETDOWN;
	return SOCKET_ERROR;
}

int
WSPAPI
WSPListen(
	SOCKET s,
	int backlog,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

WSPAPI
WSPRecv(
	SOCKET s,
	LPWSABUF lpBuffers,
	DWORD dwBufferCount,
	LPDWORD lpNumberOfBytesRecvd,
	LPDWORD lpFlags,
	LPWSAOVERLAPPED lpOverlapped,
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
	LPWSATHREADID lpThreadId,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	PUSRSCTP_CONTEXT context = NULL;
	PLIST_ENTRY entry = NULL;
	PBLOCK_RECV_ENTRY recvBlock = NULL;
	PREQUEST_RECV_ENTRY recvReq = NULL;
	size_t off, len, i;
	
	fprintf(stderr, "WSPRecv: enter\n");
	if (Upcalls.lpWPUQuerySocketHandleContext(s, (PDWORD_PTR)&context, lpErrno) == SOCKET_ERROR) {
		fprintf(stderr, "WSPRecv: leave#1\n");
		return ret;
	}
	if (context == NULL) {
		fprintf(stderr, "WSPRecv: leave#2\n");
		return ret;
	}

	fprintf(stderr, "WSPRecv: before EnterCriticalSection(%p)\n", &context->RecvLock);
	EnterCriticalSection(&context->RecvLock);
	fprintf(stderr, "WSPRecv: after EnterCriticalSection(%p)\n", &context->RecvLock);

	if (!IsListEmpty(&context->RecvBlocks)) {
		entry = RemoveHeadList(&context->RecvBlocks);
		recvBlock = (PBLOCK_RECV_ENTRY)CONTAINING_RECORD(
			entry,
			BLOCK_RECV_ENTRY,
			entry);
	}
	fprintf(stderr, "WSPRecv: recvBlock=%p\n", recvBlock);

	if (recvBlock != NULL) {
		fprintf(stderr, "WSPRecv: recvBlock->{data=%p, datalen=%u, off=%u}\n", recvBlock->data, recvBlock->datalen, recvBlock->off);
		off = 0;
		for (i = 0; i < dwBufferCount; i++) {
			fprintf(stderr, "WSPRecv: lpBuffers[%u].{buf=%p, len=%u}\n", i, lpBuffers[i].buf, lpBuffers[i].len);
			if (lpBuffers[i].buf == NULL || lpBuffers[i].len == 0)
				continue;
			
			len = lpBuffers[i].len > recvBlock->datalen - recvBlock->off ?
				recvBlock->datalen - recvBlock->off : lpBuffers[i].len;
			fprintf(stderr, "WSPRecv: len=%u\n", len);
			memcpy(lpBuffers[i].buf, recvBlock->data + recvBlock->off, len);
			recvBlock->off += len;
			off += len;
		}
		fprintf(stderr, "WSPRecv: recvBlock->{data=%p, datalen=%u, off=%u}\n", recvBlock->data, recvBlock->datalen, recvBlock->off);

		*lpNumberOfBytesRecvd = (DWORD)off;
		if (recvBlock->off < recvBlock->datalen) {
			InsertHeadList(&context->RecvBlocks, &recvBlock->entry);
		} else {
			free(recvBlock);
		}
		fprintf(stderr, "WSPRecv: before LeaveCriticalSection(%p)\n", &context->RecvLock);
		LeaveCriticalSection(&context->RecvLock);
		fprintf(stderr, "WSPRecv: after LeaveCriticalSection(%p)\n", &context->RecvLock);
	} else {
		recvReq = malloc(sizeof(REQUEST_RECV_ENTRY));
		if (recvReq == NULL) {
			LeaveCriticalSection(&context->RecvLock);
			fprintf(stderr, "WSPRecv: leave#3\n");
			return ret;
		}

		/* XXX Currently, only sync-type supported! */
		recvReq->lpBuffers = lpBuffers;
		recvReq->dwBufferCount = dwBufferCount;
		recvReq->lpNumberOfBytesRecvd = lpNumberOfBytesRecvd;
		recvReq->lpFlags = lpFlags;
		recvReq->lpOverlapped = lpOverlapped;
		recvReq->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (recvReq->hEvent == NULL) {
			free(recvReq);
			fprintf(stderr, "WSPRecv: leave#4\n");
			return ret;
		}
		fprintf(stderr, "WSPRecv: recvReq=%p, lpBuffers=%p, dwBufferCount=%u, lpNumberOfBytesRecvd=%p, lpFlags=%p, lpOverlapped=%p, hEvent=%p\n",
			recvReq, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, recvReq->hEvent);

		InsertTailList(&context->RecvRequests, &recvReq->entry);
		fprintf(stderr, "WSPRecv: before LeaveCriticalSection(%p)\n", &context->RecvLock);
		LeaveCriticalSection(&context->RecvLock);
		fprintf(stderr, "WSPRecv: after LeaveCriticalSection(%p)\n", &context->RecvLock);

		printf("Before Wait!\n");
		WaitForSingleObject(recvReq->hEvent, INFINITE);
		printf("NumberOfBytesRecvd=%u\n", *(recvReq->lpNumberOfBytesRecvd));

		free(recvReq);
	}

	return 0;
}

int
WSPAPI
WSPRecvDisconnect(
	SOCKET s,
	LPWSABUF lpInboundDisconnectData,
	LPINT lpErrno)
{
	*lpErrno = WSAEOPNOTSUPP;
	return SOCKET_ERROR;
}

int
WSPAPI
WSPRecvFrom(
	SOCKET s,
	LPWSABUF lpBuffers,
	DWORD dwBufferCount,
	LPDWORD lpNumberOfBytesRecvd,
	LPDWORD lpFlags,
	struct sockaddr* lpFrom,
	LPINT lpFromlen,
	LPWSAOVERLAPPED lpOverlapped,
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
	LPWSATHREADID lpThreadId,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPSelect(
	int nfds,
	fd_set* readfds,
	fd_set* writefds,
	fd_set* exceptfds,
	const struct timeval* timeout,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPSend(
	SOCKET s,
	LPWSABUF lpBuffers,
	DWORD dwBufferCount,
	LPDWORD lpNumberOfBytesSent,
	DWORD dwFlags,
	LPWSAOVERLAPPED lpOverlapped,
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
	LPWSATHREADID lpThreadId,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPSendDisconnect(
	SOCKET s,
	LPWSABUF lpOutboundDisconnectData,
	LPINT lpErrno)
{
	*lpErrno = WSAEOPNOTSUPP;
	return SOCKET_ERROR;
}

int
WSPAPI
WSPSendTo(
	SOCKET s,
	LPWSABUF lpBuffers,
	DWORD dwBufferCount,
	LPDWORD lpNumberOfBytesSent,
	DWORD dwFlags,
	const struct sockaddr* lpTo,
	int iTolen,
	LPWSAOVERLAPPED lpOverlapped,
	LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
	LPWSATHREADID lpThreadId,
	LPINT lpErrno)
{
	*lpErrno = WSAEOPNOTSUPP;
	return SOCKET_ERROR;
}

int
WSPAPI
WSPSetSockOpt(
	SOCKET s,
	int level,
	int optname,
	const char* optval,
	int optlen,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPShutdown(
	SOCKET s,
	int how,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

SOCKET
WSPAPI
WSPSocket(
	int af,
	int type,
	int protocol,
	LPWSAPROTOCOL_INFO lpProtocolInfo,
	GROUP g,
	DWORD dwFlags,
	LPINT lpErrno)
{
	SOCKET socket = INVALID_SOCKET;
	struct sctp_udpencaps encaps;
	PUSRSCTP_CONTEXT context;

	context = malloc(sizeof(USRSCTP_CONTEXT));
	if (context == NULL) {
		return socket;
	}
	if (!InitializeCriticalSectionAndSpinCount(&context->RecvLock, 1500)) {
		free(context);
		return socket;
	}
	InitializeListHead(&context->RecvRequests);
	InitializeListHead(&context->RecvBlocks);

	/* XXX */
	context->sock = usrsctp_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP, receive_cb, NULL, 0, context);
	if (context->sock == NULL) {
		free(context);
		return socket;
	}

	memset(&encaps, 0, sizeof(struct sctp_udpencaps));
	encaps.sue_address.ss_family = AF_INET6;
	encaps.sue_port = htons(9899);
	if (usrsctp_setsockopt(
			context->sock, IPPROTO_SCTP, SCTP_REMOTE_UDP_ENCAPS_PORT,
			(const void*)&encaps, (socklen_t)sizeof(struct sctp_udpencaps)
			) < 0
		) {
		usrsctp_close(context->sock);
		free(context);
		return socket;
	}

	socket = Upcalls.lpWPUCreateSocketHandle(
		lpProtocolInfo->dwCatalogEntryId,
		(DWORD_PTR)context,
		lpErrno
		);

	return socket;
}

int
WSPAPI
WSPStringToAddress(
	LPWSTR AddressString,
	INT AddressFamily,
	LPWSAPROTOCOL_INFOW lpProtocolInfo,
	LPSOCKADDR lpAddress,
	LPINT lpAddressLength,
	LPINT lpErrno)
{
	int ret = SOCKET_ERROR;
	return ret;
}

int
WSPAPI
WSPStartup(
	WORD wVersionRequested,
	LPWSPDATA lpWSPData,
	LPWSAPROTOCOL_INFOW lpProtocolInfo,
	WSPUPCALLTABLE UpcallTable,
	LPWSPPROC_TABLE lpProcTable)
{
	int ret = 0;

	Upcalls = UpcallTable;

	lpProcTable->lpWSPAccept = WSPAccept;
	lpProcTable->lpWSPAddressToString = WSPAddressToString;
	lpProcTable->lpWSPAsyncSelect = WSPAsyncSelect;
	lpProcTable->lpWSPBind = WSPBind;
	lpProcTable->lpWSPCancelBlockingCall = WSPCancelBlockingCall;
	lpProcTable->lpWSPCleanup = WSPCleanup;
	lpProcTable->lpWSPCloseSocket = WSPCloseSocket;
	lpProcTable->lpWSPConnect = WSPConnect;
	lpProcTable->lpWSPDuplicateSocket = WSPDuplicateSocket;
	lpProcTable->lpWSPEnumNetworkEvents = WSPEnumNetworkEvents;
	lpProcTable->lpWSPEventSelect = WSPEventSelect;
	lpProcTable->lpWSPGetOverlappedResult = WSPGetOverlappedResult;
	lpProcTable->lpWSPGetPeerName = WSPGetPeerName;
	lpProcTable->lpWSPGetSockName = WSPGetSockName;
	lpProcTable->lpWSPGetSockOpt = WSPGetSockOpt;
	lpProcTable->lpWSPGetQOSByName = WSPGetQOSByName;
	lpProcTable->lpWSPIoctl = WSPIoctl;
	lpProcTable->lpWSPJoinLeaf = WSPJoinLeaf;
	lpProcTable->lpWSPListen = WSPListen;
	lpProcTable->lpWSPRecv = WSPRecv;
	lpProcTable->lpWSPRecvDisconnect = WSPRecvDisconnect;
	lpProcTable->lpWSPRecvFrom = WSPRecvFrom;
	lpProcTable->lpWSPSelect = WSPSelect;
	lpProcTable->lpWSPSend = WSPSend;
	lpProcTable->lpWSPSendDisconnect = WSPSendDisconnect;
	lpProcTable->lpWSPSendTo = WSPSendTo;
	lpProcTable->lpWSPSetSockOpt = WSPSetSockOpt;
	lpProcTable->lpWSPShutdown = WSPShutdown;
	lpProcTable->lpWSPSocket = WSPSocket;
	lpProcTable->lpWSPStringToAddress = WSPStringToAddress;

	lpWSPData->wVersion = MAKEWORD(2, 2);
	lpWSPData->wHighVersion = MAKEWORD(2, 2);

#if 0
	hIPv4Dll = LoadLibrary(TEXT("wship"));
	if (hIPv4Dll != NULL) {
		WSHAddressToString = (PWSH_ADDRESS_TO_STRING)GetProcAddress(hIPv4Dll, "WSHAddressToString");
		WSHStringToAddress = (PWSH_STRING_TO_ADDRESS)GetProcAddress(hIPv4Dll, "WSHStringToAddress");
	}

	hIPv6Dll = LoadLibrary(TEXT("wship6"));
	if (hIPv6Dll != NULL) {
		WSHAddressToStringIPv6 = (PWSH_ADDRESS_TO_STRING)GetProcAddress(hIPv6Dll, "WSHAddressToString");
		WSHStringToAddressIPv6 = (PWSH_STRING_TO_ADDRESS)GetProcAddress(hIPv6Dll, "WSHStringToAddress");
	}
#endif

	usrsctp_init(9899, NULL);
	usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL); /* XXX */

	return ret;
}

BOOLEAN
WINAPI
DllMain(
    IN PVOID DllHandle,
    IN ULONG Reason,
    IN PVOID Context OPTIONAL)
{
	switch (Reason) {
	case DLL_PROCESS_ATTACH:
		DisableThreadLibraryCalls(DllHandle);
		break;

	case DLL_THREAD_ATTACH:
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_DETACH:
	default:
		break;
	}

	return TRUE;
}